package com.xugui.learn.alibaba.agents;

import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.hook.Hook;
import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook;
import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig;
import com.alibaba.cloud.ai.graph.agent.hook.modelcalllimit.ModelCallLimitHook;
import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIDetectionHook;
import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIType;
import com.alibaba.cloud.ai.graph.agent.hook.pii.RedactionStrategy;
import com.alibaba.cloud.ai.graph.agent.hook.summarization.SummarizationHook;
import com.alibaba.cloud.ai.graph.agent.interceptor.Interceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.contextediting.ContextEditingInterceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.todolist.TodoListInterceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.toolemulator.ToolEmulatorInterceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.toolretry.ToolRetryInterceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.toolselection.ToolSelectionInterceptor;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.xugui.learn.alibaba.hook.CustomStopConditionHook;
import com.xugui.learn.alibaba.interceptor.DynamicPromptInterceptor;
import com.xugui.learn.alibaba.interceptor.ToolErrorInterceptor;
import com.xugui.learn.alibaba.tools.UserLocationTool;
import com.xugui.learn.alibaba.tools.WeatherForLocationTool;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 生成对应的阿里的智能体
 */
@Configuration
@RequiredArgsConstructor
public class DashScopeAgentConfig {

    private final DashScopeChatModel dashScopeChatModel;

    private static final String SYSTEM_PROMPT = """
            You are an expert weather forecaster, who speaks in puns.
        
            You have access to two tools:
        
            - get_weather_for_location: use this to get the weather for a specific location
            - get_user_location: use this to get the user's location
        
            If a user asks you for the weather, make sure you know the location.
            If you can tell from the question that they mean wherever they are,
            use the get_user_location tool to find their location.
            """;

    private static final String customSchema = """
            请按照以下JSON格式输出：
            {
                "title": "标题",
                "content": "内容",
                "style": "风格"
            }
            """;

    private static final String instruction = """
            你是一个经验丰富的软件架构师。
            
            在回答问题时，请：
            1. 首先理解用户的核心需求
            2. 分析可能的技术方案
            3. 提供清晰的建议和理由
            4. 如果需要更多信息，主动询问
            
            保持专业、友好的语气。
            """;

    /**
     * 创建一个阿里的智能体
     *
     * @return 阿里的智能体
     */
    @Bean
    public ReactAgent getDashScopeReactAgent() {
        // 创建工具回调
        ToolCallback getWeatherTool = FunctionToolCallback
                .builder("getWeatherForLocation", new WeatherForLocationTool())
                .description("Get weather for a given city")
                .inputType(String.class)
                // .inputSchema()
                // .toolMetadata()
                // .toolCallResultConverter()
                .build();

        ToolCallback getUserLocationTool = FunctionToolCallback
                .builder("getUserLocation", new UserLocationTool())
                .description("Retrieve user location based on user ID")
                .inputType(String.class)
                .build();

        // 创建消息压缩 Hook
        Hook summarizationHook = SummarizationHook.builder()
                .model(dashScopeChatModel)
                .maxTokensBeforeSummary(4000)  // 触发摘要之前的最大 token 数
                .messagesToKeep(20)  // 摘要后保留的最新消息数
                .build();

        // 创建 hook 在 Agent 执行的关键点插入自定义逻辑
        Hook humanInTheLoopHook = HumanInTheLoopHook.builder()
                .approvalOn("getWeatherTool",
                        ToolConfig.builder()
                        .description("Please confirm tool execution.")
                        .build()
                )
                .approvalOn("deleteDataTool", "删除数据")
                .build();  // 需要 check pointer 来维护跨中断的状态

        // 限制模型调用次数
        Hook modelCallLimitHook = ModelCallLimitHook.builder().runLimit(5).build();

        // 检测和处理对话中的个人身份信息
        Hook pii = PIIDetectionHook.builder()
                .piiType(PIIType.EMAIL)
                .strategy(RedactionStrategy.REDACT)
                .applyToInput(true)
                .build();

        // 自动重试失败的工具调用，具有可配置的指数退避
        Interceptor toolRetryInterceptor = ToolRetryInterceptor.builder()
                .maxRetries(2)
                .onFailure(ToolRetryInterceptor.OnFailureBehavior.RETURN_MESSAGE)
                .build();

        // 在执行工具之前强制执行一个规划步骤，以概述 Agent 将要采取的步骤。
        Interceptor todoListInterceptor = TodoListInterceptor.builder().build();

        // 使用一个 LLM 来决定在多个可用工具之间选择哪个工具
        Interceptor toolSelectionInterceptor = ToolSelectionInterceptor.builder().build();

        // 在没有实际执行工具的情况下，使用 LLM 模拟工具的输出
        Interceptor toolEmulatorInterceptor = ToolEmulatorInterceptor.builder().model(dashScopeChatModel).build();

        // 在将上下文发送给 LLM 之前对其进行修改，以注入、删除或修改信息
        Interceptor contextEditingInterceptor = ContextEditingInterceptor.builder()
                .trigger(120000).clearAtLeast(60000).build();

        // 生产环境中使用 Redis 记忆
        // RedisSaver redisSaver = new RedisSaver(redissonClient);

        return ReactAgent.builder()
                .name("weather_agent")
                .model(dashScopeChatModel)
                .tools(getWeatherTool, getUserLocationTool)
                // .systemPrompt(SYSTEM_PROMPT)
                .instruction(instruction)  // 指定更加详细的指令
                .hooks(
                        humanInTheLoopHook,
                        modelCallLimitHook,   // 限制模型调用次数
                        new CustomStopConditionHook()  // 自定义停止条件
                )
                .interceptors(
                        new ToolErrorInterceptor(),  // 工具出错后的拦截
                        new DynamicPromptInterceptor()  // 动态提示词拦截
                )
                .saver(new MemorySaver())  // 保存记忆 生产环境：使用 RedisSaver、MongoSaver 等持久化存储替代
                // .outputType(ResponseFormat.class)  // 指定输出类型  类型安全，适合结构固定的场景
                .outputSchema(customSchema)   // 指定输出类型  灵活性高，适合动态或复杂的输出格式
                .build();
    }
}
